﻿#region License
// Copyright 2010 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;

namespace MediaWiki.MediaWikiConverter.Utilities.Registry
{
  public sealed class RegistryKeyEx : MarshalByRefObject, IDisposable
  {
    private static readonly IRegistry Registry;

    private readonly bool _isRoot;
    private readonly bool _isWritable;
    private readonly string _qualifiedName;
    internal object _data;
    internal bool _disposed;

    static RegistryKeyEx()
    {
      Registry = new Win32Registry();
    }

    /// <summary>
    ///	Construct an instance of a root registry key entry.
    /// </summary>
    internal RegistryKeyEx(RegistryHive hiveId, string keyName)
    {
      _data = hiveId;
      _qualifiedName = keyName;
      _isRoot = true;
      _isWritable = true; // always consider root writable
    }

    /// <summary>
    ///	Construct an instance of a registry key entry.
    /// </summary>
    internal RegistryKeyEx(object data, string keyName, bool writable)
    {
      _data = data;
      _qualifiedName = keyName;
      _isRoot = false;
      _isWritable = writable;
    }

    public string Name
    {
      get { return _qualifiedName; }
    }

    public int SubKeyCount
    {
      get
      {
        AssertNotDisposed();

        return Registry.SubKeyCount(this);
      }
    }

    public int ValueCount
    {
      get
      {
        AssertNotDisposed();

        return Registry.ValueCount(this);
      }
    }

    void IDisposable.Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    ~RegistryKeyEx()
    {
      Dispose(false);
    }

    private void Dispose(bool dispose)
    {
      if (dispose)
        Flush();

      if (_isRoot)
        return;

      Registry.Close(this);
      _disposed = true;
    }

    public void Flush()
    {
      Registry.Flush(this);
    }

    public void Close()
    {
      Dispose(true);
    }

    public void SetValue(string name, object value)
    {
      SetValue(name, value, RegistryValueKind.Unknown);
    }

    public void SetValue(string name, object value, RegistryValueKind valueKind)
    {
      AssertNotDisposed();

      if (value == null)
        throw new ArgumentNullException("value");

      if (!IsWritable)
        throw new UnauthorizedAccessException("Cannot write to the registry key.");

      Registry.SetValue(this, name, value, valueKind);
    }

    public RegistryKeyEx OpenSubKey(string keyName)
    {
      return OpenSubKey(keyName, false);
    }

    public RegistryKeyEx OpenSubKey(string keyName, bool writable)
    {
      return OpenSubKey(keyName, false, false);
    }

    public RegistryKeyEx OpenSubKey(string keyName, bool writable, bool ignoreWow64)
    {
      AssertNotDisposed();
      AssertKeyNameNotNull(keyName);

      return Registry.OpenSubKey(this, keyName, writable, ignoreWow64);
    }

    public object GetValue(string name)
    {
      return GetValue(name, null);
    }

    public object GetValue(string name, object defaultValue)
    {
      return GetValue(name, defaultValue, RegistryValueOptions.None);
    }

    public object GetValue(string name, object defaultValue, RegistryValueOptions options)
    {
      AssertNotDisposed();

      return Registry.GetValue(this, name, defaultValue, options);
    }

    public RegistryKeyEx CreateSubKey(string subkey)
    {
      return CreateSubKey(subkey, false);
    }

    public RegistryKeyEx CreateSubKey(string subkey, bool ignoreWow64)
    {
      AssertNotDisposed();
      AssertKeyNameNotNull(subkey);
      if (subkey.Length > 255)
        throw new ArgumentException("keyName length is larger than 255 characters", subkey);

      AssertWritable();

      return Registry.CreateSubKey(this, subkey, ignoreWow64);
    }

    public void DeleteSubKey(string subkey)
    {
      DeleteSubKey(subkey, true);
    }

    public void DeleteSubKey(string subkey, bool throwOnMissingSubKey)
    {
      DeleteSubKey(subkey, throwOnMissingSubKey, false);
    }

    public void DeleteSubKey(string subkey, bool throwOnMissingSubKey, bool ignoreWow64)
    {
      AssertNotDisposed();
      AssertKeyNameNotNull(subkey);
      AssertWritable();

      RegistryKeyEx child = OpenSubKey(subkey, false, ignoreWow64);

      if (child == null)
      {
        if (throwOnMissingSubKey)
          throw new ArgumentException("Cannot delete a subkey tree because the subkey does not exist.");

        return;
      }

      if (child.SubKeyCount > 0)
        throw new InvalidOperationException("Registry key has subkeys and recursive removes are not supported by this method.");

      child.Close();

      Registry.DeleteKey(this, subkey, throwOnMissingSubKey, ignoreWow64);
    }

    public void DeleteSubKeyTree(string keyName)
    {
      DeleteSubKeyTree(keyName, false);
    }

    public void DeleteSubKeyTree(string keyName, bool ignoreWow64)
    {
      AssertNotDisposed();
      AssertKeyNameNotNull(keyName);

      RegistryKeyEx child = OpenSubKey(keyName, true, ignoreWow64);
      if (child == null)
        throw new ArgumentException("Cannot delete a subkey tree because the subkey does not exist.");

      child.DeleteChildKeysAndValues(ignoreWow64);
      child.Close();
      DeleteSubKey(keyName, false, ignoreWow64);
    }

    public void DeleteValue(string value)
    {
      DeleteValue(value, true);
    }

    public void DeleteValue(string value, bool shouldThrowWhenKeyMissing)
    {
      AssertNotDisposed();
      AssertKeyNameNotNull(value);
      AssertWritable();

      Registry.DeleteValue(this, value, shouldThrowWhenKeyMissing);
    }

    public string[] GetSubKeyNames()
    {
      AssertNotDisposed();
      return Registry.GetSubKeyNames(this);
    }

    public string[] GetValueNames()
    {
      AssertNotDisposed();
      return Registry.GetValueNames(this);
    }

    public override string ToString()
    {
      return _qualifiedName;
    }

    internal bool IsRoot
    {
      get { return _isRoot; }
    }

    internal bool IsWritable
    {
      get { return _isWritable; }
    }

    private void AssertNotDisposed()
    {
      if (_disposed)
        throw new ObjectDisposedException(typeof(RegistryKeyEx).FullName);
    }

    private void AssertWritable()
    {
      if (!IsWritable)
        throw new UnauthorizedAccessException("Cannot write to the registry key.");
    }

    private void AssertKeyNameNotNull(string subKeyName)
    {
      if (subKeyName == null)
        throw new ArgumentNullException();
    }

    private void DeleteChildKeysAndValues(bool ignoreWow64)
    {
      if (_isRoot)
        return;

      string[] subKeys = GetSubKeyNames();
      foreach (string subKey in subKeys)
      {
        RegistryKeyEx sub = OpenSubKey(subKey, true, ignoreWow64);
        sub.DeleteChildKeysAndValues(ignoreWow64);
        sub.Close();
        DeleteSubKey(subKey, false, ignoreWow64);
      }

      string[] values = GetValueNames();
      foreach (string value in values)
      {
        DeleteValue(value, false);
      }
    }

    internal static string DecodeString(byte[] data)
    {
      string stringRep = Encoding.Unicode.GetString(data);
      int idx = stringRep.IndexOf('\0');
      if (idx != -1)
        stringRep = stringRep.TrimEnd('\0');
      return stringRep;
    }

    internal static IOException CreateMarkedForDeletionException()
    {
      throw new IOException("Illegal operation attempted on a registry key that has been marked for deletion.");
    }
  }
}